﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AlgoritimoGenetico.dados;
using AlgoritimoGenetico.utils;


namespace AlgoritimoGenetico.operadores
{
    public class Operadores
    {
        /// <summary>
        /// Avalia o fitness do cromossono
        /// </summary>
        /// <param name="cromossomo"> Cromossomo que será avaliado</param>
        /// <returns>valor inteiro do fitness do cromossomo</returns>
        public static float Avaliar(Cromossomo cromossomo, List<byte> configuracaoUsuario)
        {
            float avaliacao = 0;
            float avalDist = 0;
            float pesoFitness = 3;
            float pesoDistancia = 2;
            float pesoRepeticao = 5;
            foreach (Gene gen in cromossomo.Genes)
            {
              float geneFit = Avaliar(gen, configuracaoUsuario);
              gen.Fitness = geneFit;
              avaliacao += geneFit;
            }
            for (int i = 0; i < cromossomo.Genes.Count - 1; i++)
                avalDist += RepositorioCidades.GetDistanciaCidades(cromossomo.Genes[i].Nome, cromossomo.Genes[i + 1].Nome);

            float avalRepeticao = _AvaliarRepeticaoCidadesCromosso(cromossomo); 

            cromossomo.AvalDist = avalDist;
            cromossomo.AvalRepet = avalRepeticao;

            avaliacao = (
                            (pesoFitness * avaliacao) +
                            (pesoDistancia * (1 / (avalDist == 0 ? 1 : avalDist))) +
                            (pesoRepeticao *  (1 / (avalRepeticao == 0? 1: avalRepeticao)))
                        ) / (pesoFitness + pesoDistancia + pesoRepeticao); 

            return avaliacao;
        }
        private static float _AvaliarRepeticaoCidadesCromosso(Cromossomo cromossomo)
        {
            float avaliacao = 0;
            int i,j = 0;
            int iguais = 0;
            for (i = 0; i < cromossomo.Genes.Count - 1; i ++)
            {
                iguais = 0;
                for (j = i + 1; j < cromossomo.Genes.Count; j++)
                {
                    if (cromossomo.Genes[i].Nome.Equals(cromossomo.Genes[j].Nome))
                        iguais++;
                }
                if(iguais > 0)
                    avaliacao += _AvaliarRepeticao(iguais, cromossomo);
            }

            return avaliacao;
        }

        private static float _AvaliarRepeticao(int qtdIguais, Cromossomo cromossomo)
        {
            float aval = 0;
            aval =  (float)((float)qtdIguais / (float)cromossomo.Genes.Count);
            
            return  - 20 * aval;
        }
        /// <summary>
        /// Avalia um Gene
        /// </summary>
        /// <param name="gene">Gene a ser avaliado</param>
        /// <returns>O fitness do gene</returns>
        public static float Avaliar(Gene gene, List<byte> configuracaoUsuario)
        {
            float avaliacao = 0;
            List<byte> fatores = configuracaoUsuario;
            
            for(int i=0;i<25;i++)
            {
                avaliacao += gene.Caracteristicas[i] * fatores[i];
            }
            return avaliacao;
        }

        public static Cromossomo Cruzamento(Cromossomo pai, Cromossomo mae, List<Gene> populacao, float fitnessTotalGenes)
        {
          int tamFilho = Utils.Rand.Next(1, 11);
          Cromossomo filho = new Cromossomo();
          List<Gene> total = pai.CloneGenes();
          total.AddRange(mae.CloneGenes());
          total.Sort(delegate(Gene g1, Gene g2) { return g2.Fitness.CompareTo(g1.Fitness); });

          int count = 0;
          while (filho.Genes.Count < tamFilho && count < total.Count)
          {
              if (filho.Genes.Count >= tamFilho)
                break;
              if (filho.Genes.Count < tamFilho)
              {
                  filho.Genes.Add(total[count]);
                  count++;
              }
          };


          while (filho.Genes.Count < tamFilho)
          {
              if (filho.Genes.Count >= tamFilho)
                break;
              filho.Genes.Add(_Roleta(populacao, fitnessTotalGenes));
          }

          return filho;
        }

        public static void Mutacao(Cromossomo cromossomo)
        {
            int num = Utils.Rand.Next(3);
            switch (num)
            {
                case 0: // Alterando a última cidade
                    while (true)
                    {
                        utils.Utils.Shuffle<Cidade>(RepositorioCidades.Cidades);
                        Cidade cidade1 = RepositorioCidades.Cidades[0];

                        if (!cromossomo.Genes.Any(c => c.Nome.Equals(cidade1.Nome)))
                        {
                            int index = cromossomo.Genes.Count;
                            cromossomo.Genes[index-1].Nome = cidade1.Nome;
                            cromossomo.Genes[index-1].Caracteristicas = new List<float>();
                            foreach(float carac in cidade1.Caracteristicas)
                                cromossomo.Genes[index-1].Caracteristicas.Add(carac);
                            break;
                        }
                    }
                    break;
                case 1: // Removendo a última cidade
                    if (cromossomo.Genes.Count > 1)
                        cromossomo.Genes.RemoveAt(cromossomo.Genes.Count - 1);
            
                    break;

                case 2: // Adicionando uma cidade
                    if(cromossomo.Genes.Count < 10)
                        while (true)
                        {
                            utils.Utils.Shuffle<Cidade>(RepositorioCidades.Cidades);
                            Cidade cidade1 = RepositorioCidades.Cidades[0];

                            if (!cromossomo.Genes.Any(c => c.Nome.Equals(cidade1.Nome)))
                            {
                                Gene gene = new Gene();
                                gene.Nome = cidade1.Nome;
                                gene.Caracteristicas = new List<float>();
                                foreach (float carac in cidade1.Caracteristicas)
                                    gene.Caracteristicas.Add(carac);
                                
                                cromossomo.Genes.Add(gene);
                                break;
                            }
                        }
                        break;

                default:
                    throw new Exception("Numero gerado tem q ser menor que 3");
            }
        }

        public static void selecaoPais()
        {

            int a = 0;
        }

        /// <summary>
        /// Seleciona aleatoria probabilisticamente os melhores cromossomos de acordo com a taxa de elitismo
        /// </summary>
        /// <param name="populacao">População atual</param>
        /// <param name="taxaElitismo">Taxa de elitismo a ser aplicada</param>
        /// <returns>Lista de melhores cromossomos</returns>
        public static List<Cromossomo> Elitismo(List<Cromossomo> populacao, double taxaElitismo)
        {
            int size = populacao.Count;
            int x =  (int) (size * taxaElitismo);

            List<Cromossomo> popElist = new List<Cromossomo>();

            for (int i = 0; i < x; i++)
            {
                popElist.Add(_Roleta(populacao));

            }
            
            return popElist;
        }

        /// <summary>
        /// Método da roleta aplicado a Cromossomo. Usa o Fitness do cromossomo como base para o cálculo. Retorna cromossomo sorteado ou null caso aconteça erro.
        /// </summary>
        /// <param name="cromossomos">Lista de cromossomos para sorteio pela roleta</param>
        /// <returns>Cromossomo sorteado. Null caso dê erro.</returns>
        private static Cromossomo _Roleta(List<Cromossomo> cromossomos)
        {  
            Cromossomo retorno = null;
            float fitnessTotalCromossomos = 0;
            float fitnessTotalProb = 0;
            float rand = 0;
            float sum = 0;
            float[] p = new float[cromossomos.Count];

            foreach(Cromossomo crom in cromossomos)
                fitnessTotalCromossomos += crom.Fitness;

            for (int i = 0; i < cromossomos.Count; i++)
            {
                p[i] = fitnessTotalCromossomos == 0 ? cromossomos[i].Fitness : cromossomos[i].Fitness / fitnessTotalCromossomos;
                fitnessTotalProb += p[i];
            }

            rand = (float)Utils.Rand.NextDouble();

            for (int i = 0; i < cromossomos.Count; i++)
            {
                sum += p[i];
                if (rand <= sum)
                {
                    retorno = cromossomos[i].Clone();
                    break;
                }
            }
            return retorno;
        }

        /// <summary>
        /// Método da roleta para conjunto de genes. Usa o fitness do gene como base para o cálculo. Retorna gene sorteado ou null caso contrário
        /// </summary>
        /// <param name="genes">Lista de genes para o sorteio.</param>
        /// <returns>Gene</returns>
        internal static Gene _Roleta(List<Gene> genes, float fitnessTotalGenes)
        {
            Gene retorno = null;
            float fitTotal = 0;
            float rand = 0;
            float sum = 0;
            float[] p = new float[genes.Count];

            for (int i = 0; i < genes.Count; i++)
            {
                p[i] = fitnessTotalGenes == 0 ? genes[i].Fitness : genes[i].Fitness / fitnessTotalGenes;
                fitTotal += p[i];
            }

            rand = (float)Utils.Rand.NextDouble();

            for (int i = 0 ; i < genes.Count; i++)
            {
                sum += p[i];
                if (rand <= sum)
                {
                    retorno = genes[i].Clone();
                    break;
                }
            }

            if (retorno == null)
                retorno = genes[Utils.Rand.Next(0, genes.Count)];
            return retorno;
        }
    }
}